{
  "openapi": "3.1.0",
  "info": {
    "title": "Cloud Storage Local API",
    "description": "Quản lý tệp, lượt tải lên, lượt xóa và thẻ tệp của cloud storage MoreLogin thông qua MoreLogin Local API.\n\n**URL cơ sở**: `http://127.0.0.1:40000`\n\nCác đường dẫn của Local API dùng tiền tố `/api`.\n",
    "version": "2026-09-05"
  },
  "servers": [
    {
      "url": "http://127.0.0.1:40000"
    }
  ],
  "tags": [
    {
      "name": "Cloud Storage",
      "description": "Truy vấn hạn mức và mức sử dụng lưu trữ"
    },
    {
      "name": "Cloud Storage Files",
      "description": "Truy vấn, tải lên và xóa tệp trên lưu trữ đám mây"
    },
    {
      "name": "Cloud Storage File Tags",
      "description": "Quản lý các thẻ gắn với tệp trên lưu trữ đám mây"
    }
  ],
  "x-tagGroups": [
    {
      "name": "Cloud Storage",
      "tags": [
        "Cloud Storage",
        "Cloud Storage Files",
        "Cloud Storage File Tags"
      ]
    }
  ],
  "paths": {
    "/api/cloudstorage/info": {
      "get": {
        "summary": "Truy vấn thông tin lưu trữ đám mây",
        "description": "Trả về hạn mức cloud storage của nhóm và mức sử dụng hiện tại.\n- **Điều kiện tiên quyết**: nhóm phải đã có bản ghi cloud storage. **Một nhóm chưa từng được cấp cloud storage sẽ nhận về lỗi, không phải giá trị dung lượng bằng 0** — bản ghi được tạo khi mua dung lượng, không phải được tạo trễ bởi lệnh gọi này.\n- **Tác động**: không có. Chỉ đọc. An toàn khi thử lại với thời gian chờ tăng dần có giới hạn.\n- **Đơn vị**: `capacity` và `usedCapacity` là số byte.\n- **Độ chính xác**: `usedCapacity` là một bộ đếm tích lũy được cập nhật khi tải lên và khi xóa, không phải một phép tổng tính trực tiếp trên bảng tệp, nên nó có thể lệch so với tổng `fileSize` trong `/cloudstorage/file/page`.\n- **Giá trị**: `capacityStatus` chỉ bằng `1` khi một hạn mức dương đã bị dùng hết; `isExpired` so sánh ngày hết hạn với thời điểm hiện tại. Những tệp chưa từng được xác nhận tải lên không được tính vào `usedCapacity`.\n\nCác mã lỗi nghiệp vụ đã ghi nhận: `39001`. Xem [Error Codes](../Getting%20Started/error-codes.md) để biết ý nghĩa từng mã. Mọi thao tác cũng có thể trả về các mã dùng chung.",
        "operationId": "queryCloudStorageInfoLocal",
        "tags": [
          "Cloud Storage"
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/StorageInfoResponse"
          }
        }
      }
    },
    "/api/cloudstorage/file/page": {
      "post": {
        "summary": "Phân trang tệp trên lưu trữ đám mây",
        "description": "Liệt kê theo trang các tệp Cloud Storage.\n- **Tác động**: không có. Chỉ đọc. An toàn khi thử lại với thời gian chờ tăng dần có giới hạn.\n- **Khả năng nhìn thấy**: **chỉ những tệp đã xác nhận tải lên mới được liệt kê** — một tệp có URL đã ký trước nhưng chưa bao giờ hoàn tất sẽ không xuất hiện. Thành viên không có quyền xem toàn bộ dữ liệu chỉ thấy những tệp do chính mình tải lên.\n- **Bộ lọc**: `fileName` khớp một phần; `tagIds` khớp theo logic HOẶC; kết quả được sắp từ mới đến cũ và không thể đổi thứ tự này.\n- **Lưu ý**: một phần tử trong `tagInfo` có thể bằng null khi một thẻ bị xóa trong lúc vẫn còn gắn với tệp.",
        "operationId": "pageCloudStorageFilesLocal",
        "tags": [
          "Cloud Storage Files"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/FilePageRequest"
              },
              "example": {
                "pageNo": 1,
                "pageSize": 20,
                "fileName": "report",
                "tagIds": [
                  "1800000000000001"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/FilePageResponse"
          }
        }
      }
    },
    "/api/cloudstorage/upload/init": {
      "post": {
        "summary": "Yêu cầu URL tải lên có ký trước theo lô",
        "description": "Đặt trước các bản ghi cloud storage và lấy URL tải lên đã ký trước.\n- **Điều kiện tiên quyết**: nhóm phải có bản ghi cloud storage và bản ghi đó không được hết hạn. Phần tên cơ sở của mỗi tệp giới hạn 60 ký tự và phải là duy nhất cả trong phạm vi yêu cầu lẫn so với mọi tệp đang có của nhóm — **việc đối chiếu dựa trên tên không kèm phần mở rộng**, nên `a.png` sẽ xung đột với `a.mp4` đã tồn tại.\n- **Giới hạn**: toàn nhóm chỉ được có tối đa 100 lượt tải lên chưa xác nhận cùng lúc.\n- **Tác động**: chèn một bản ghi cho mỗi tên tệp với trạng thái đang tải lên và kích thước 0, rồi trả về `{id, fileName, presignedUrl}` cho từng tệp.\n- **Hạn mức**: **dung lượng không được kiểm tra ở bước này.** Nó bị trừ khi bạn xác nhận lượt tải lên, nên init có thể thành công mà complete vẫn thất bại vì hết dung lượng.\n- **Hoàn tất**: đồng bộ. **Mỗi URL đã ký trước có hiệu lực trong một giờ**; hãy `PUT` dữ liệu tệp lên đó, sau đó gọi `/cloudstorage/upload/complete`.\n\nCác mã lỗi nghiệp vụ đã ghi nhận: `39001`, `39037`, `39044`, `39045`, `39047`, `39050`, `39051`. Xem [Error Codes](../Getting%20Started/error-codes.md) để biết ý nghĩa từng mã. Mọi thao tác cũng có thể trả về các mã dùng chung.",
        "operationId": "batchRequestCloudStoragePresignedUploadUrlsLocal",
        "tags": [
          "Cloud Storage Files"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UploadInitRequest"
              },
              "example": {
                "fileNames": [
                  "example.jpg"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/UploadInitResponse"
          }
        }
      }
    },
    "/api/cloudstorage/upload/complete": {
      "post": {
        "summary": "Xác nhận hoàn tất tải lên",
        "description": "Xác nhận một tệp cloud storage đã được tải lên.\n- **Điều kiện tiên quyết**: bản ghi tệp phải thuộc nhóm của bạn và đối tượng phải đã nằm trong bộ lưu trữ — dịch vụ đọc ETag và độ dài của nó để kiểm chứng. Kích thước phải lớn hơn 0 và nhỏ hơn 2 GB.\n- **Tác động**: trừ hạn mức lưu trữ của nhóm một cách nguyên tử, rồi đánh dấu bản ghi là đã tải lên và lưu kích thước thật. Cho đến khi việc này thành công, tệp **không xuất hiện trong `/cloudstorage/file/page`** và vẫn chiếm một trong 100 suất chờ xác nhận.\n- **Khi thất bại, đối tượng đã tải lên sẽ bị xóa.** Gọi quá sớm, vượt giới hạn kích thước, hay không còn dung lượng đều khiến dữ liệu bị xóa khỏi bộ lưu trữ và bản ghi ở lại trạng thái chưa xác nhận. Cách khắc phục là `/cloudstorage/upload/init` mới rồi tải lên lại, không phải thử lại lệnh gọi này.\n- **Hoàn tất**: đồng bộ và idempotent sau khi đã thành công — lần gọi thứ hai trả về cùng tệp mà không trừ hạn mức thêm lần nữa.\n- **Lưu ý**: bản ghi không bao giờ được xác nhận sẽ tự động được dọn sau 24 giờ.\n\nCác mã lỗi nghiệp vụ đã ghi nhận: `39001`, `39011`, `39014`, `39041`, `39046`, `39047`. Xem [Error Codes](../Getting%20Started/error-codes.md) để biết ý nghĩa từng mã. Mọi thao tác cũng có thể trả về các mã dùng chung.",
        "operationId": "confirmCloudStorageUploadCompleteLocal",
        "tags": [
          "Cloud Storage Files"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UploadCompleteRequest"
              },
              "example": {
                "id": "1800000000000001"
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/UploadCompleteResponse"
          }
        }
      }
    },
    "/api/cloudstorage/file/delete/batch": {
      "post": {
        "summary": "Xóa tệp trên lưu trữ đám mây theo lô",
        "description": "Xóa các tệp Cloud Storage theo ID.\n- **Điều kiện tiên quyết**: mọi ID phải tra cứu được trong nhóm của bạn; **nếu chỉ một ID không tra cứu được thì cả lô bị từ chối và hoàn tác**, kể cả những ID đã bị xóa trước đó.\n- **Tác động**: đây là xóa mềm. Dung lượng được giải phóng trong cùng một giao dịch, và các đối tượng đã lưu được xóa theo cơ chế nỗ lực tối đa — lỗi phía lưu trữ được ghi log nhưng không làm lệnh gọi thất bại, nên một đối tượng có thể tồn tại lâu hơn bản ghi của nó. Các liên kết thẻ không được dọn dẹp.\n- **Hoàn tất**: đồng bộ. Hãy xác nhận dung lượng đã giải phóng bằng `/cloudstorage/info`.\n\nCác mã lỗi nghiệp vụ đã ghi nhận: `39011`. Xem [Error Codes](../Getting%20Started/error-codes.md) để biết ý nghĩa từng mã. Mọi thao tác cũng có thể trả về các mã dùng chung.",
        "operationId": "batchDeleteCloudStorageFilesLocal",
        "tags": [
          "Cloud Storage Files"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/FileDeleteRequest"
              },
              "example": {
                "ids": [
                  "1800000000001001",
                  "1800000000001002"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/VoidDataResponse"
          }
        }
      }
    },
    "/api/cloudstorage/file/tag/set": {
      "post": {
        "summary": "Đặt thẻ cho tệp",
        "description": "Thay thế toàn bộ thẻ của một tệp Cloud Storage.\n- **Điều kiện tiên quyết**: mỗi lần gọi một `fileId`, và `tagIds` không được rỗng và phải tham chiếu tới các thẻ Cloud Storage thuộc nhóm của bạn. Thẻ của profile trình duyệt sẽ bị từ chối.\n- **Tác động**: các thẻ không có trong yêu cầu sẽ bị bỏ khỏi tệp và những thẻ còn thiếu sẽ được thêm vào. **Ở đây không có cách nào bỏ hết thẻ** vì danh sách rỗng bị từ chối.\n- **Hoàn tất**: đồng bộ và idempotent với cùng một request body.\n\nCác mã lỗi nghiệp vụ đã ghi nhận: `39011`, `39048`, `39049`. Xem [Error Codes](../Getting%20Started/error-codes.md) để biết ý nghĩa từng mã. Mọi thao tác cũng có thể trả về các mã dùng chung.",
        "operationId": "setCloudStorageFileTagsLocal",
        "tags": [
          "Cloud Storage File Tags"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/FileTagChangeRequest"
              },
              "example": {
                "fileId": "1800000000001001",
                "tagIds": [
                  "1800000000000101",
                  "1800000000000102"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/VoidDataResponse"
          }
        }
      }
    },
    "/api/cloudstorage/file/tag/add": {
      "post": {
        "summary": "Thêm thẻ cho tệp",
        "description": "Thêm thẻ cho một tệp Cloud Storage mà không bỏ các thẻ hiện có.\n- **Điều kiện tiên quyết**: mỗi lần gọi một `fileId`, và `tagIds` không được rỗng và phải tham chiếu tới các thẻ Cloud Storage thuộc nhóm của bạn.\n- **Tác động**: chỉ thêm; các thẻ hiện có được giữ nguyên. Dùng biến thể thay thế nếu bạn muốn thay hết.\n- **Hoàn tất**: đồng bộ và idempotent — thêm một thẻ mà tệp đã có thì không thay đổi gì.\n\nCác mã lỗi nghiệp vụ đã ghi nhận: `39011`, `39048`, `39049`. Xem [Error Codes](../Getting%20Started/error-codes.md) để biết ý nghĩa từng mã. Mọi thao tác cũng có thể trả về các mã dùng chung.",
        "operationId": "addCloudStorageFileTagsLocal",
        "tags": [
          "Cloud Storage File Tags"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/FileTagChangeRequest"
              },
              "example": {
                "fileId": "1800000000001001",
                "tagIds": [
                  "1800000000000103"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/VoidDataResponse"
          }
        }
      }
    },
    "/api/cloudstorage/file/tag/query": {
      "post": {
        "summary": "Truy vấn thẻ của tệp",
        "description": "Trả về các thẻ đang gắn với một hoặc nhiều tệp Cloud Storage.\n- **Điều kiện tiên quyết**: mọi `fileId` phải tra cứu được trong nhóm của bạn; **một ID không xác định sẽ làm cả lệnh gọi thất bại** chứ không trả về dữ liệu một phần.\n- **Tác động**: không có. Chỉ đọc. An toàn khi thử lại với thời gian chờ tăng dần có giới hạn.\n- **Lưu ý**: `tagIds` được đọc từ các liên kết, còn chi tiết thẻ lấy từ các thẻ hiện hữu, nên một thẻ bị xóa trong lúc vẫn còn liên kết sẽ xuất hiện trong `tagIds` mà không có mục tương ứng ở phần chi tiết. Độ dài hai danh sách có thể khác nhau.",
        "operationId": "queryCloudStorageFileTagsLocal",
        "tags": [
          "Cloud Storage File Tags"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/FileTagQueryRequest"
              },
              "example": {
                "fileIds": [
                  "1800000000001001"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/FileTagsResponse"
          }
        }
      }
    },
    "/api/cloudstorage/tag/all": {
      "get": {
        "summary": "Truy vấn tất cả thẻ tệp cloud storage",
        "description": "Trả về mọi thẻ tệp Cloud Storage trong nhóm.\n- **Tác động**: không có. Chỉ đọc. An toàn khi thử lại với thời gian chờ tăng dần có giới hạn.\n- **Lưu ý**: endpoint không phân trang, và thẻ Cloud Storage không có giới hạn số lượng phía server, nên phản hồi sẽ lớn dần theo số thẻ mà nhóm bạn tạo.",
        "operationId": "queryAllCloudStorageFileTagsLocal",
        "tags": [
          "Cloud Storage File Tags"
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/TagListResponse"
          }
        }
      }
    },
    "/api/cloudstorage/tag/create": {
      "post": {
        "summary": "Tạo thẻ tệp cloud storage",
        "description": "Tạo một thẻ tệp Cloud Storage.\n- **Điều kiện tiên quyết**: `tagName` không được để trống.\n- **Tác động**: tạo thẻ với một màu chọn từ bảng màu cố định. **Tên không được kiểm tra trùng lặp và không có giới hạn số lượng**, nên gọi lại sẽ tạo ra một thẻ thứ hai cùng tên.\n- **Hoàn tất**: đồng bộ; phản hồi mang thẻ mới.",
        "operationId": "createCloudStorageFileTagLocal",
        "tags": [
          "Cloud Storage File Tags"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TagCreateRequest"
              },
              "example": {
                "tagName": "Invoice"
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/TagResponse"
          }
        }
      }
    },
    "/api/cloudstorage/tag/edit": {
      "post": {
        "summary": "Sửa thẻ tệp cloud storage",
        "description": "Đổi tên một thẻ tệp Cloud Storage.\n- **Điều kiện tiên quyết**: thẻ phải tồn tại trong nhóm của bạn và phải là thẻ Cloud Storage.\n- **Tác động**: chỉ tên thay đổi; màu và mọi liên kết hiện có đều được giữ, nên các tệp hiển thị tên mới ngay lập tức.\n- **Hoàn tất**: đồng bộ. Không có khóa lạc quan — các lần đổi tên đồng thời sẽ lấy lần ghi cuối cùng.",
        "operationId": "editCloudStorageFileTagLocal",
        "tags": [
          "Cloud Storage File Tags"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TagEditRequest"
              },
              "example": {
                "id": "1800000000000101",
                "tagName": "Invoice 2026"
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/BooleanDataResponse"
          }
        }
      }
    },
    "/api/cloudstorage/tag/delete/batch": {
      "post": {
        "summary": "Xóa thẻ tệp cloud storage",
        "description": "Xóa các thẻ tệp Cloud Storage.\n- **Điều kiện tiên quyết**: mọi ID phải là thẻ Cloud Storage trong nhóm của bạn; **một ID không hợp lệ sẽ làm cả lô thất bại**.\n- **Tác động**: các thẻ bị xóa nhưng **liên kết của chúng với tệp không được dọn dẹp**. Hãy lường trước hai hệ quả: `/cloudstorage/file/tag/query` vẫn tiếp tục trả về ID thẻ đã xóa mà không có chi tiết tương ứng, và `tagInfo` trong `/cloudstorage/file/page` có thể chứa phần tử null. Lọc tệp theo ID của một thẻ đã xóa sẽ không trả về gì.\n- **Hoàn tất**: đồng bộ, và không được bọc trong một giao dịch.",
        "operationId": "deleteCloudStorageFileTagsLocal",
        "tags": [
          "Cloud Storage File Tags"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TagDeleteRequest"
              },
              "example": {
                "ids": [
                  "1800000000000101"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/BooleanDataResponse"
          }
        }
      }
    }
  },
  "security": [],
  "components": {
    "schemas": {
      "ApiResponseBase": {
        "type": "object",
        "properties": {
          "code": {
            "type": "integer",
            "description": "Mã kết quả. `0` nghĩa là thành công; các mã khác cho biết có ngoại lệ.",
            "example": 0
          },
          "msg": {
            "type": [
              "string",
              "null"
            ],
            "description": "Thông báo lỗi.",
            "example": null
          },
          "requestId": {
            "type": "string",
            "description": "ID yêu cầu của tác vụ. Trường này có thể xuất hiện khi yêu cầu đi qua API gateway.",
            "example": "1d4f3ea968664593860b94b35d4ebf5e"
          }
        },
        "required": [
          "code",
          "msg"
        ]
      },
      "StorageInfo": {
        "type": "object",
        "properties": {
          "capacity": {
            "type": "string",
            "description": "Tổng dung lượng cloud storage theo byte.",
            "example": "10737418240"
          },
          "usedCapacity": {
            "type": "string",
            "description": "Dung lượng cloud storage đã dùng theo byte.",
            "example": "536870912"
          },
          "expireDate": {
            "type": "string",
            "pattern": "^\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}$",
            "description": "Ngày hết hạn của cloud storage."
          },
          "capacityStatus": {
            "type": "integer",
            "description": "Trạng thái dung lượng: 0 = bình thường, 1 = đã đầy.",
            "example": 0
          },
          "isExpired": {
            "type": "boolean",
            "description": "Gói đăng ký đã hết hạn hay chưa.",
            "example": false
          }
        }
      },
      "FilePageRequest": {
        "type": "object",
        "properties": {
          "pageNo": {
            "type": "integer",
            "description": "Số trang hiện tại, bắt đầu từ 1. Mặc định là 1.",
            "minimum": 1,
            "default": 1,
            "example": 1
          },
          "pageSize": {
            "type": "integer",
            "description": "Số mục mỗi trang. Mặc định là 10.",
            "minimum": 1,
            "default": 10,
            "example": 20
          },
          "fileName": {
            "type": "string",
            "description": "Từ khóa tên tệp để tìm kiếm gần đúng.",
            "example": "report"
          },
          "tagIds": {
            "type": "array",
            "description": "Lọc tệp theo ID thẻ.",
            "items": {
              "type": "string"
            },
            "example": [
              "1800000000000101"
            ]
          },
          "fileType": {
            "type": "integer",
            "description": "Bộ lọc theo loại tệp: 0 = hình ảnh, 1 = video, 2 = ứng dụng, 3 = âm thanh, 4 = tài liệu, 5 = khác.",
            "example": 0
          },
          "fileIds": {
            "type": "array",
            "description": "Lọc tệp theo ID tệp.",
            "items": {
              "type": "string"
            },
            "example": [
              "1800000000001001"
            ]
          }
        }
      },
      "TagInfo": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "ID thẻ.",
            "example": "1800000000000101"
          },
          "tagSource": {
            "type": "string",
            "description": "Nguồn của thẻ, ví dụ ENV, PROXY.",
            "example": "ENV"
          },
          "tagName": {
            "type": "string",
            "description": "Tên thẻ.",
            "example": "Invoice"
          },
          "tagType": {
            "type": "string",
            "description": "Kiểu thẻ: TAG hoặc GROUP.",
            "example": "TAG"
          },
          "tagIcon": {
            "type": "string",
            "description": "Biểu tượng của thẻ.",
            "example": ""
          }
        }
      },
      "CloudStorageFile": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "ID tệp trên lưu trữ đám mây.",
            "example": "1800000000001001"
          },
          "fileName": {
            "type": "string",
            "description": "Tên tệp (không kèm phần mở rộng).",
            "example": "example"
          },
          "fileExt": {
            "type": "string",
            "description": "Phần mở rộng của tệp.",
            "example": "jpg"
          },
          "fileSize": {
            "type": "string",
            "description": "Kích thước tệp theo byte.",
            "example": "204800"
          },
          "fileType": {
            "type": "integer",
            "description": "Loại tệp: 0 = hình ảnh, 1 = video, 2 = ứng dụng, 3 = âm thanh, 4 = tài liệu, 5 = khác.",
            "example": 0
          },
          "tagInfo": {
            "type": "array",
            "description": "Chi tiết các thẻ đang gắn với tệp.",
            "items": {
              "$ref": "#/components/schemas/TagInfo"
            }
          },
          "createDate": {
            "type": "string",
            "pattern": "^\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}$",
            "description": "Thời điểm tải lên."
          },
          "updateDate": {
            "type": "string",
            "pattern": "^\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}$",
            "description": "Thời điểm cập nhật."
          }
        }
      },
      "FilePageData": {
        "type": "object",
        "properties": {
          "current": {
            "type": "string",
            "example": "1"
          },
          "pages": {
            "type": "string",
            "example": "7"
          },
          "total": {
            "type": "string",
            "example": "128"
          },
          "dataList": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CloudStorageFile"
            }
          }
        },
        "required": [
          "current",
          "pages",
          "total",
          "dataList"
        ]
      },
      "UploadInitRequest": {
        "type": "object",
        "properties": {
          "fileNames": {
            "type": "array",
            "minItems": 1,
            "description": "Danh sách tên tệp cần tải lên (kèm phần mở rộng), ví dụ photo.jpg, video.mp4.",
            "items": {
              "type": "string"
            },
            "example": [
              "photo.jpg",
              "video.mp4"
            ]
          }
        },
        "required": [
          "fileNames"
        ]
      },
      "UploadInitResult": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "ID tệp trên lưu trữ đám mây.",
            "example": "1800000000000001"
          },
          "fileName": {
            "type": "string",
            "description": "Tên tệp (kèm phần mở rộng).",
            "example": "photo.jpg"
          },
          "presignedUrl": {
            "type": "string",
            "description": "URL tải lên đã ký trước của S3. Dùng HTTPS PUT để tải nội dung tệp lên.",
            "example": "https://example.com/presigned-upload-url"
          }
        }
      },
      "UploadCompleteRequest": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "ID tệp cloud storage do API URL tải lên đã ký trước trả về.",
            "example": "1800000000000001"
          }
        },
        "required": [
          "id"
        ]
      },
      "FileDeleteRequest": {
        "type": "object",
        "properties": {
          "ids": {
            "type": "array",
            "minItems": 1,
            "items": {
              "type": "string"
            },
            "description": "ID các tệp cloud storage cần xóa.",
            "example": [
              "1800000000001001",
              "1800000000001002"
            ]
          }
        },
        "required": [
          "ids"
        ]
      },
      "FileTagChangeRequest": {
        "type": "object",
        "properties": {
          "fileId": {
            "type": "string",
            "description": "ID tệp trên lưu trữ đám mây.",
            "example": "1800000000001001"
          },
          "tagIds": {
            "type": "array",
            "minItems": 1,
            "items": {
              "type": "string"
            },
            "description": "Danh sách ID thẻ.",
            "example": [
              "1800000000000101",
              "1800000000000102"
            ]
          }
        },
        "required": [
          "fileId",
          "tagIds"
        ]
      },
      "FileTagQueryRequest": {
        "type": "object",
        "properties": {
          "fileIds": {
            "type": "array",
            "minItems": 1,
            "items": {
              "type": "string"
            },
            "description": "ID các tệp cloud storage.",
            "example": [
              "1800000000001001"
            ]
          }
        },
        "required": [
          "fileIds"
        ]
      },
      "FileTagResultData": {
        "type": "object",
        "properties": {
          "fileId": {
            "type": "string",
            "description": "ID tệp trên lưu trữ đám mây.",
            "example": "1800000000001001"
          },
          "tagIds": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "ID các thẻ đang gắn với tệp.",
            "example": [
              "1800000000000101"
            ]
          },
          "tagInfo": {
            "type": "array",
            "description": "Chi tiết các thẻ.",
            "items": {
              "$ref": "#/components/schemas/TagInfo"
            }
          }
        }
      },
      "TagCreateRequest": {
        "type": "object",
        "properties": {
          "tagName": {
            "type": "string",
            "description": "Tên thẻ.",
            "example": "Invoice"
          }
        },
        "required": [
          "tagName"
        ]
      },
      "TagEditRequest": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "ID thẻ.",
            "example": "1800000000000101"
          },
          "tagName": {
            "type": "string",
            "description": "Tên thẻ.",
            "example": "Invoice 2026"
          }
        },
        "required": [
          "id",
          "tagName"
        ]
      },
      "TagDeleteRequest": {
        "type": "object",
        "properties": {
          "ids": {
            "type": "array",
            "minItems": 1,
            "items": {
              "type": "string"
            },
            "description": "ID các thẻ cần xóa.",
            "example": [
              "1800000000000101"
            ]
          }
        },
        "required": [
          "ids"
        ]
      }
    },
    "responses": {
      "StorageInfoResponse": {
        "description": "Thành công",
        "content": {
          "application/json": {
            "schema": {
              "allOf": [
                {
                  "$ref": "#/components/schemas/ApiResponseBase"
                },
                {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/StorageInfo"
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              ]
            }
          }
        }
      },
      "FilePageResponse": {
        "description": "Thành công",
        "content": {
          "application/json": {
            "schema": {
              "allOf": [
                {
                  "$ref": "#/components/schemas/ApiResponseBase"
                },
                {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FilePageData"
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              ]
            }
          }
        }
      },
      "UploadInitResponse": {
        "description": "Thành công",
        "content": {
          "application/json": {
            "schema": {
              "allOf": [
                {
                  "$ref": "#/components/schemas/ApiResponseBase"
                },
                {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/UploadInitResult"
                      }
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              ]
            }
          }
        }
      },
      "UploadCompleteResponse": {
        "description": "Thành công",
        "content": {
          "application/json": {
            "schema": {
              "allOf": [
                {
                  "$ref": "#/components/schemas/ApiResponseBase"
                },
                {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/CloudStorageFile"
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              ]
            }
          }
        }
      },
      "VoidDataResponse": {
        "description": "Thành công",
        "content": {
          "application/json": {
            "schema": {
              "allOf": [
                {
                  "$ref": "#/components/schemas/ApiResponseBase"
                },
                {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "null",
                      "example": null
                    }
                  }
                }
              ]
            }
          }
        }
      },
      "FileTagsResponse": {
        "description": "Thành công",
        "content": {
          "application/json": {
            "schema": {
              "allOf": [
                {
                  "$ref": "#/components/schemas/ApiResponseBase"
                },
                {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/FileTagResultData"
                      }
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              ]
            }
          }
        }
      },
      "TagListResponse": {
        "description": "Thành công",
        "content": {
          "application/json": {
            "schema": {
              "allOf": [
                {
                  "$ref": "#/components/schemas/ApiResponseBase"
                },
                {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/TagInfo"
                      }
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              ]
            }
          }
        }
      },
      "TagResponse": {
        "description": "Thành công",
        "content": {
          "application/json": {
            "schema": {
              "allOf": [
                {
                  "$ref": "#/components/schemas/ApiResponseBase"
                },
                {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/TagInfo"
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              ]
            }
          }
        }
      },
      "BooleanDataResponse": {
        "description": "Thành công",
        "content": {
          "application/json": {
            "schema": {
              "allOf": [
                {
                  "$ref": "#/components/schemas/ApiResponseBase"
                },
                {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "boolean",
                      "example": true
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              ]
            }
          }
        }
      }
    }
  }
}